﻿// 寮변鑒（文件编码转换工具）
// 包含必要的头文件
#include "pic.h"       // 图像解析相关函数
#include "code.h"      // 二维码编码相关函数
#include "ffmpeg.h"    // FFmpeg 视频处理封装
#include "ImgDecode.h" // 图像解码相关结构

// 定义调试宏：显示图像并等待按键，添加缩放功能
#define Show_Img(src) do {         \
    double scale_percent = 0.5;    \
    cv::Size newSize(static_cast<int>(src.cols * scale_percent), static_cast<int>(src.rows * scale_percent)); \
    cv::Mat resized;               \
    cv::resize(src, resized, newSize, 0, 0, cv::INTER_AREA); \
    cv::imshow("DEBUG", resized);  \
    cv::waitKey();                 \
} while (0);

//----------------------------- 函数声明 -----------------------------
// 文件转视频函数
// 参数：
//   - filePath: 输入文件路径
//   - videoPath: 输出视频路径
//   - timLim: 时间限制（毫秒），控制生成的最大帧数
//   - fps: 输出视频的帧率
// 返回值：0成功，1失败
int FileToVideo(const char* filePath, const char* videoPath, int timLim = INT_MAX, int fps = 10);

// 视频转文件函数
// 参数：
//   - videoPath: 输入视频路径
//   - filePath: 输出文件路径
// 返回值：0成功，1失败
int VideoToFile(const char* videoPath, const char* filePath);

//----------------------------- 函数实现 -----------------------------

// 文件转视频核心逻辑
int FileToVideo(const char* filePath, const char* videoPath, int timLim, int fps) {
    // 1. 读取输入文件到内存
    FILE* fp = fopen(filePath, "rb");
    if (fp == nullptr) return 1; // 文件打开失败
    fseek(fp, 0, SEEK_END);
    int size = ftell(fp);       // 获取文件大小
    rewind(fp);
    char* temp = (char*)malloc(size); // 动态分配内存
    if (temp == nullptr) {
        fclose(fp);
        return 1; // 内存分配失败
    }
    fread(temp, 1, size, fp);  // 读取全部文件内容
    fclose(fp);

    // 2. 创建临时目录存放生成的二维码帧图像
    system("md outputImg"); // Windows下创建目录

    // 3. 调用编码模块生成二维码图像序列
    // 参数说明：
    //   - temp: 输入数据
    //   - size: 数据长度
    //   - "outputImg": 输出目录
    //   - "png": 图像格式
    //   - 1LL*fps*timLim/1000: 最大帧数（根据时间限制计算）
    Code::Main(temp, size, "outputImg", "png", 1LL * fps * timLim / 1000);

    // 4. 调用FFmpeg将图像序列合成视频
    // 参数说明：
    //   - rawFrameRates: 输入帧率（默认30）
    //   - outputFrameRates: 输出帧率（默认60）
    //   - kbps: 视频码率（0表示自动）
    FFMPEG::ImagetoVideo("outputImg", "png", videoPath, fps, 60, 100000);

    // 5. 清理临时目录和内存
    system("rd /s /q outputImg"); // 删除临时目录
    free(temp);
    return 0;
}

// 视频转文件核心逻辑
int VideoToFile(const char* videoPath, const char* filePath) {
    char imgName[256]; // 存储帧图像路径
    system("rd /s /q inputImg");  // 清理旧临时目录
    system("md inputImg");        // 创建新目录存放提取的帧

    // 1. 多线程提取视频帧：FFmpeg在后台运行
    bool isThreadOver = false;
    std::thread th([&] {
        FFMPEG::VideotoImage(videoPath, "inputImg", "jpg"); // 提取帧到inputImg目录
        isThreadOver = true; // 标记线程完成
        });

    // 2. 逐帧解析视频中的二维码数据
    int precode = -1;          // 记录前一帧的帧号，用于连续性检查
    std::vector<unsigned char> outputFile; // 存储最终输出的文件数据
    bool hasStarted = false;   // 是否已找到起始帧
    bool ret = 0;              // 返回值（错误标志）

    for (int i = 1;; ++i) {
        // 生成当前帧的图像路径（例如 inputImg\00001.jpg）
        snprintf(imgName, 256, "inputImg\\%05d.jpg", i);
        FILE* fp;

        // 等待图像文件生成（直到文件存在或线程结束）
        do {
            fp = fopen(imgName, "rb");
        } while (fp == nullptr && !isThreadOver);

        // 文件未生成且线程已结束 -> 退出循环
        if (fp == nullptr) {
            puts("视频解析失败：视频可能不完整或损坏");
            ret = 1;
            break;
        }
        fclose(fp);

        // 3. 读取图像并解析二维码
        cv::Mat srcImg = cv::imread(imgName, 1), disImg;
        if (ImgParse::Main(srcImg, disImg)) { // 图像预处理和定位失败
            continue; // 跳过无效帧
        }
        //Show_Img(disImg);
        // 4. 解码二维码数据
        ImageDecode::ImageInfo imageInfo;
        if (ImageDecode::Main(disImg, imageInfo)) { // 解码失败
            continue;
        }
        //Show_Img(disImg);
        // 5. 处理起始帧和结束帧
        if (!hasStarted) {
            if (imageInfo.IsStart) hasStarted = true; // 找到起始帧
            else continue; // 忽略起始前的无效帧
        }

        // 6. 检查帧连续性（防止丢帧）
        if (precode == imageInfo.FrameBase) continue; // 重复帧
        if (((precode + 1) & UINT16_MAX) != imageInfo.FrameBase) {
           puts("错误：检测到帧丢失，解析结果可能不完整");
            
            continue;
        }

        // 7. 保存有效数据
        printf("成功解析帧 %d\n", imageInfo.FrameBase);
        precode = (precode + 1) & UINT16_MAX;
        outputFile.insert(outputFile.end(), imageInfo.Info.begin(), imageInfo.Info.end());

        // 8. 遇到结束帧则终止
        if (imageInfo.IsEnd) break;
    }

    // 9. 收尾工作
    if (ret == 0) {
        th.join(); // 等待FFmpeg线程结束
        printf("\n视频解析成功！\n文件大小：%lld字节\n总帧数：%d\n",
            outputFile.size(), precode);

        // 写入输出文件
        FILE* fp = fopen(filePath, "wb");
        if (fp == nullptr) return 1;
        fwrite(outputFile.data(), 1, outputFile.size(), fp);
        fclose(fp);
    }
    return ret;
}

// 主函数：解析命令行参数
int main(int argc, char* argv[]) {
    constexpr bool type = false; // 编译时模式开关
    // true: 文件转视频模式
    // false: 视频转文件模式

// 根据模式选择处理逻辑
    if constexpr (type) { // 文件转视频
        if (argc == 4) {  // 参数：输入文件 输出视频 时间限制(ms)
            return FileToVideo(argv[1], argv[2], std::stoi(argv[3]));
        }
        else if (argc == 5) { // 参数：输入文件 输出视频 时间限制(ms) 帧率
            return FileToVideo(argv[1], argv[2], std::stoi(argv[3]), std::stoi(argv[4]));
        }
    }
    else { // 视频转文件
        if (argc == 3) { // 参数：输入视频 输出文件
            return VideoToFile(argv[1], argv[2]);
        }
    }

    // 参数错误提示
    puts("参数错误！正确用法：");
    puts("文件转视频模式：程序 输入文件 输出视频 [时间限制(ms)] [帧率]");
    puts("视频转文件模式：程序 输入视频 输出文件");
    return 1;
}